/*
 * Created on Feb 20, 2007
 */
package sandboxmv.gamedata;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;

import sandboxmv.common.SMVPhysicalConstants;
import sandboxmv.entities.Hero;
import sandboxmv.map.Room;
import sandboxmv.map.Surface;
import ecologylab.game2d.common.CompressedVector;
import ecologylab.game2d.terrain.Wall;
import ecologylab.xml.library.geom.Vector2d;

/**
 * @author Zach Toups (toupsz@gmail.com)
 */
public class ControlledHero implements MouseListener, MouseMotionListener,
        KeyListener, SMVPhysicalConstants
{
    Hero     hero;

    Room     currentRoom = null;

    GameData gameData    = null;

    boolean  walkingLeft = false, walkingRight = false, jumping = false,
            restingContact = false;

    /**
     * 
     */
    public ControlledHero()
    {
        // TODO Auto-generated constructor stub
    }

    /**
     * @param x
     * @param y
     * @param dir
     * @param speed
     * @param id
     * @param inGameOrder
     */
    public ControlledHero(double x, double y, int speed, String id,
            int inGameOrder, GameData gameData, String startingRoom)
    {
        this.hero = new Hero(x, y, speed, id, inGameOrder, startingRoom);

        this.gameData = gameData;
        this.gameData.setControlledHero(this);
    }

    public void update()
    {
        if (currentRoom == null)
        { // pull down the current room
            this.currentRoom = gameData.getRooms().getRoom(hero.getRoomUID());
        }

        this.runPhysicalSimulationWithCollisionResponse(TIME_STEP);

    }

    private void runPhysicalSimulationWithCollisionResponse(double tS)
    {
        double sTS = tS;
        hero.addAcc(CompressedVector.scalarMultiply(currentRoom.getGravity(),
                tS));
        hero.applyIntendedVel(tS);
        
        ArrayList<Surface> collisionSurfaces = null;

        // determine where the seeker SHOULD go
        while (tS > 0)
        {

            hero.proposeSimPos(tS);
            hero.proposeSimVel(tS);
            
            // see if there's a collision surface
            collisionSurfaces = currentRoom.determineCollision(hero.getPos(),
                    hero.getSimPos(), tS);

            if (collisionSurfaces.size() > 0)
            { // there was a collision, maybe more than one; get the fraction
                // of the timestep
                // all of them have the same fraction of the timestep, because
                // they MUST be at the same time to be in the set; so we can
                // just look at the first one
                double h = collisionSurfaces.get(0).determineCollision(
                        hero.getPos(), hero.getSimPos());

                h = Math.abs(h) - EPSILON;

                if (h < 0)
                {
                    h = 0;
                }
                
                // move back to the collision time
                hero.proposeSimVel(tS * h);
                hero.proposeSimPos(tS * h);

                hero.acceptProposal();

                for (Surface cWall : collisionSurfaces)
                {
                    Vector2d sn = cWall.getSurfaceNormal();

                    CompressedVector vn = CompressedVector.scalarMultiply(sn,
                            CompressedVector.dot(sn, hero.getVel()));
                    CompressedVector vs = CompressedVector.sub(hero.getVel(), vn);

                    hero.setVel(CompressedVector.scalarMultiply(CompressedVector.sub(hero.getVel(), vn), 1.0 - cWall.getFriction()));
                    //apply surface friction
                }

                tS = tS * (1.0 - h);
            }
            else
            {
                hero.acceptProposal();
                tS = 0;
            }
        }
        
        hero.unapplyIntendedVel(sTS);
        
        hero.clearAcc();
    }

    public void keyPressed(KeyEvent arg0)
    {
        if (arg0.getKeyCode() == KeyEvent.VK_SPACE)
        {
            hero.addForwardImpulse(300);
            jumping = true;
            restingContact = false;
        }

        if (arg0.getKeyCode() == KeyEvent.VK_A & !walkingLeft)
        {
            hero.addLeftImpulse();
            walkingLeft = true;
        }

        if (arg0.getKeyCode() == KeyEvent.VK_D & !walkingRight)
        {
            hero.addRightImpulse();
            walkingRight = true;
        }
    }

    public void keyReleased(KeyEvent arg0)
    {
        if (arg0.getKeyCode() == KeyEvent.VK_A && walkingLeft)
        {
            hero.addRightImpulse();
            walkingLeft = false;
        }

        if (arg0.getKeyCode() == KeyEvent.VK_D && walkingRight)
        {
            hero.addLeftImpulse();
            walkingRight = false;
        }
    }

    public void keyTyped(KeyEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    public void mouseClicked(MouseEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    public void mouseEntered(MouseEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    public void mouseExited(MouseEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    public void mousePressed(MouseEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    public void mouseReleased(MouseEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    public void mouseDragged(MouseEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    /**
     * Adjusts the hero's aim to point at the mouse cursor location.
     * 
     * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
     */
    public void mouseMoved(MouseEvent arg0)
    {
        // TODO Auto-generated method stub

    }

    /**
     * @return the currentRoom
     */
    public Room getCurrentRoom()
    {
        if (currentRoom == null)
        { // pull down the current room
            this.currentRoom = gameData.getRooms().getRoom(hero.getRoomUID());
        }

        return currentRoom;
    }

    /**
     * @return the hero
     */
    public Hero getHero()
    {
        return hero;
    }

}
